use std::collections::HashMap;

use bytes::{BufMut, Bytes, BytesMut};
use tokio::net::TcpStream;

use crate::conn::{ConnectionConfig, SslMode};
use crate::error::{Result, VerticaError};
use crate::msgs::*;

pub async fn perform_authentication(
    stream: &mut TcpStream,
    config: &ConnectionConfig,
    auth_request: AuthenticationRequest,
) -> Result<()> {
    match auth_request.auth_type {
        0 => Ok(()), // Authentication successful
        3 => handle_cleartext_password(stream, config).await,
        5 => handle_md5_password(stream, config, &auth_request.data).await,
        _ => Err(VerticaError::Authentication(
            format!("Unsupported authentication method: {}", auth_request.auth_type)
        )),
    }
}

async fn handle_cleartext_password(
    stream: &mut TcpStream,
    config: &ConnectionConfig,
) -> Result<()> {
    let password = config.password.clone()
        .ok_or_else(|| VerticaError::Authentication("Password required but not provided".to_string()))?;
    
    let mut buf = BytesMut::new();
    buf.extend_from_slice(password.as_bytes());
    buf.put_u8(0);
    
    let message = Message::new(b'p', buf.freeze());
    let bytes = message.encode();
    stream.write_all(&bytes).await.map_err(|e| VerticaError::Io(e))
}

async fn handle_md5_password(
    stream: &mut TcpStream,
    config: &ConnectionConfig,
    data: &Bytes,
) -> Result<()> {
    if data.len() < 4 {
        return Err(VerticaError::Protocol("Invalid MD5 authentication data".to_string()));
    }
    
    let salt = &data[..4];
    let password = config.password.clone()
        .ok_or_else(|| VerticaError::Authentication("Password required but not provided".to_string()))?;
    
    let hashed = md5_password(&config.user, &password, salt);
    
    let mut buf = BytesMut::new();
    buf.extend_from_slice(hashed.as_bytes());
    buf.put_u8(0);
    
    let message = Message::new(b'p', buf.freeze());
    let bytes = message.encode();
    stream.write_all(&bytes).await.map_err(|e| VerticaError::Io(e))
}

fn md5_password(user: &str, password: &str, salt: &[u8]) -> String {
    use md5::{Md5, Digest};
    
    // MD5(password + user)
    let mut hasher = Md5::new();
    hasher.update(password.as_bytes());
    hasher.update(user.as_bytes());
    let hash1 = hasher.finalize();
    
    // MD5(hash1 + salt)
    let mut hasher = Md5::new();
    hasher.update(&hash1);
    hasher.update(salt);
    let hash2 = hasher.finalize();
    
    format!("md5{:x}", hash2)
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_md5_password() {
        let user = "testuser";
        let password = "testpass";
        let salt = b"salt";
        
        let result = md5_password(user, password, salt);
        assert!(result.starts_with("md5"));
        assert_eq!(result.len(), 35); // "md5" + 32 hex chars
    }
}